#include "icqoscar.h"
#include "UserStatusSpy.h"

#define ICQEVENTTYPE_WAS_FOUND 2013 //db event )

CUserStatusSpy::CUserStatusSpy(CIcqProto* proto) : m_pProto(proto)
{
}


CUserStatusSpy::~CUserStatusSpy(void)
{
}

void CUserStatusSpy::QueueCookie(DWORD dwCookie, DWORD dwUIN)
{
	icq_lock lock(m_oLock);
	UserToCheck user;
	user.dwCookie = dwCookie;
	user.dwUIN = dwUIN;
	m_deqUsers.push_back(user);
}

void CUserStatusSpy::CheckUserStatus(HANDLE hContact)
{
    DWORD dwUIN = m_pProto->getSettingDword(hContact, UNIQUEIDSETTING, 0);

    if (m_pProto->CheckContactCapabilities(hContact, WAS_FOUND))
        makeUserOffline(hContact); // ensure that contact was made offline, before being checked

    // getting invisibility via status message
	// NOTE: Partially works in CIcqProto::handleRecvServMsgError for unadded users who are not in Invsible mode
    //DWORD dwCookie = m_pProto->icq_sendGetAwayMsgServ(hContact, dwUIN, MTYPE_AUTOAWAY, (WORD)(m_pProto->getSettingWord(hContact, "Version", 0)==9?9:ICQ_VERSION)); // Success
	//QueueCookie(dwCookie, dwUIN);	// FIXME: It's possible that response will come earlier than cookie is queued

    // getting invisibility via xtraz notify request
	//DWORD dwCookie = sendDetectionPacket(hContact); //detect icq6 invisibility (added by [sin])
	//QueueCookie(dwCookie, dwUIN);	// FIXME: It's possible that response will come earlier than cookie is queued

    // getting invisibility via malformed url message
    //DWORD dwCookie = m_pProto->icq_sendGetAwayMsgServ(hContact, dwUIN, MTYPE_URL, (WORD)(m_pProto->getSettingWord(hContact, "Version", 0)==9?9:ICQ_VERSION)); //detect miranda invisibility (added by [sin])
	//QueueCookie(dwCookie, dwUIN);	// FIXME: It's possible that response will come earlier than cookie is queued

    //if (bASDUnauthorized)
    // Response is handled in CIcqProto::handleLocationUserInfoReply
    //icq_sendGetLocationInfo(hContact, dwUIN, 0); //method reported by D@rkNeo

	DWORD dwCookie = m_pProto->GenerateCookie(0);
	QueueCookie(dwCookie, dwUIN);
	detectViaAuth(hContact, dwCookie);
}

void CUserStatusSpy::SetUserStatus(DWORD dwUin, DWORD dwCookie, signed nStatus, HANDLE hContact)
{
    /* Statuses:

    	+ online/invisible
    	- offline

    * active *

    	-1: Recieve server Ack
    	-4: Recipient is not logged in
    	2: Received away message
    	4: Incorrect SNAC format
    	5: got caps

    * passive *

    	21: type 1 msg
    	22: type 2 msg
    	24: type 4 msg
    	25: MTN
    	26: AUTH REQ
    	27: // Added
    	28: Auth Accept or Deny
    	29: Removed from list
    */

#ifdef _DEBUG
    Netlib_Logf(NULL, "Set status cmd: %d, Cookie: 0x%X, Uin: %d", nStatus, dwCookie, dwUin);
#endif

    // TODO: Check if given UIN was in local container
    //		  search by COOKIE
	{
		icq_lock lock(m_oLock);
		std::list<UserToCheck>::iterator it = m_deqUsers.begin();
		while (it != m_deqUsers.end())
			if ((*it).dwCookie == dwCookie)
			{
				dwUin = (*it).dwUIN;
				m_deqUsers.erase(it);
				break;
			} else
				++it;
	}

    if (!dwUin && !hContact)
        return;

    hContact = m_pProto->HContactFromUIN(dwUin, 0);

    if (hContact && hContact != INVALID_HANDLE_VALUE)
    {
        char* name = NickFromHandleUtf(hContact);

        if (nStatus < 0)   // user is not here 8-(
        {
            Netlib_Logf(NULL, "%s (%d) is not here 8-(", name, dwUin);
            makeUserOffline(hContact);
        }
        else     // user is here 8-)
        {
            if (nStatus <= 5)
                Netlib_Logf(NULL, "%s (%d) is here 8-)", name, dwUin);

            if (m_pProto->getSettingWord(hContact, "Status", ID_STATUS_OFFLINE) == ID_STATUS_OFFLINE)
            {
                time_t TimeStamp = time(NULL);

                /*
                if ((nStatus <= 5) &&
                		!DBGetContactSettingByte(hContact, "CList", "Hidden", 0) &&
                		!DBGetContactSettingByte(hContact, "CList", "NotOnList", 0) &&
                		!(DBGetContactSettingDword(hContact, "Ignore","Mask1", 0) & 0x8))
                	PopUpMsg(hContact, 4);

                if((nStatus >= 21) && (nStatus <= 29))
                	PopUpMsg(hContact, 3);
                */

                if (nStatus != 5)
                {
                    m_pProto->setSettingWord(hContact, "Status", ID_STATUS_INVISIBLE);
                    m_pProto->setSettingDword(hContact, "OldLogonTS", (DWORD)TimeStamp);
                    m_pProto->ClearAllContactCapabilities(hContact);
                }

                {
                    CHECKCONTACT chk = {0};
                    chk.dbeventflag = DBEF_READ;
                    chk.dwUin = dwUin;
                    chk.hContact = hContact;
                    chk.historyevent = (chk.logtofile = TRUE);
                    chk.icqeventtype = ICQEVENTTYPE_WAS_FOUND;
                    /*
                    if(nStatus <= 5)
                    	chk.msg = "detected via ASD";
                    else
                    	if((nStatus>=21) && (nStatus<=29))
                    		chk.msg = "detected via PSD";
                    	else
                    		chk.msg = "invisible contact detected";
                    */

                    chk.PSD = -1;
                    CheckContact(chk);
                }

                m_pProto->SetContactCapabilities(hContact, WAS_FOUND);
                m_pProto->ClearContactCapabilities(hContact, CAPF_SRV_RELAY); // for compatibility

                // dim icon
                if (!m_pProto->getSettingDword(hContact, "IdleTS", 0))
                    m_pProto->setSettingDword(hContact, "IdleTS", (DWORD)TimeStamp);
            }
        }

        SAFE_FREE((void**)&name);
    }
}

void CUserStatusSpy::makeUserOffline(HANDLE hContact)
{
    WORD w;
    DWORD dw;

    if (m_pProto->getSettingWord(hContact, "Status", 0) != ID_STATUS_OFFLINE)
        m_pProto->setSettingWord(hContact, "Status", ID_STATUS_OFFLINE);

    if (dw = m_pProto->getSettingDword(hContact, "LogonTS", 0))
    {
        m_pProto->setSettingDword(hContact, "LogonTS", 0);
        m_pProto->setSettingDword(hContact, "OldLogonTS", dw);
    }

    if (dw = m_pProto->getSettingDword(hContact, "IdleTS", 0))
    {
        m_pProto->setSettingDword(hContact, "IdleTS", 0);
        m_pProto->setSettingDword(hContact, "OldIdleTS", dw);
    }

    if (dw = m_pProto->getSettingDword(hContact, "RealIP", 0))
    {
        m_pProto->setSettingDword(hContact, "RealIP", 0);
        m_pProto->setSettingDword(hContact, "OldRealIP", dw);
    }

    if (dw = m_pProto->getSettingDword(hContact, "IP", 0))
    {
        m_pProto->setSettingDword(hContact, "IP", 0);
        m_pProto->setSettingDword(hContact, "OldIP", dw);
    }

    if (w = m_pProto->getSettingWord(hContact, "UserPort", 0))
    {
        m_pProto->setSettingWord(hContact, "UserPort", 0);
        m_pProto->setSettingWord(hContact, "OldUserPort", w);
    }

    if (w = m_pProto->getSettingWord(hContact, "Version", 0))
    {
        m_pProto->setSettingWord(hContact, "Version", 0);
        m_pProto->setSettingWord(hContact, "OldVersion", w);
    }
}

void CUserStatusSpy::CheckContact(CHECKCONTACT chk)
{
    BYTE Hidden = DBGetContactSettingByte(chk.hContact, "CList", "Hidden", 0);
    BOOL Ignored = FALSE, Cancel = FALSE;
    BYTE InDb = CallService(MS_DB_CONTACT_IS, (WPARAM)chk.hContact, 0);
    BYTE NotOnList = DBGetContactSettingByte(chk.hContact,"CList","NotOnList",0);
    char popmsg[512] = {0}, eventmsghistory[512] = {0}, eventmsgfile[512] = {0};

    if (chk.hContact == NULL)
        return;

    if (!chk.dwUin)
        chk.dwUin = m_pProto->getSettingDword(chk.hContact, "UIN", 0);

    if (DBGetContactSettingDword(chk.hContact, "Ignore", "Mask1", 0) == 0x0000007F)
        Ignored = TRUE;

    if (!InDb || NotOnList)
        Cancel = TRUE;

    if (!chk.icqeventtype)
    {
        //wrong call check ... (missing event types)
        chk.historyevent = FALSE;
        chk.logtofile = FALSE;
    }

    bool bTmpContacts = true;	// FIXME: Move to settings
    bool bTmpAuthRequet = true;
    bool bTmpSendAdded = false;
    const char* TmpGroupName = "Temp";
    bool bAddTemp = true;
    if (chk.dwUin)
    {
        if (bTmpContacts)
        {
            int added;
            static BOOL TmpContact;
            chk.hContact = m_pProto->HContactFromUIN(chk.dwUin, &added);
            TmpContact = m_pProto->getSettingByte(chk.hContact, "TmpContact", 0);
            if(NotOnList && !TmpContact)
            {
                uid_str szUid;
                DWORD TmpUin;
                DBWriteContactSettingStringUtf(chk.hContact, "CList", "Group", TmpGroupName);
                DBWriteContactSettingByte(chk.hContact,"CList","Hidden", 0);
                DBWriteContactSettingByte(chk.hContact,"CList","NotOnList", bAddTemp);

                if (bAddTemp)
                    m_pProto->setSettingByte(chk.hContact, "TmpContact", bAddTemp);

                m_pProto->setSettingByte(chk.hContact, "CheckSelfRemove", 1);	//excluding from checkselfremove
                m_pProto->getContactUid(chk.hContact, &TmpUin, &szUid);

                if (bTmpAuthRequet && szUid)
                    m_pProto->icq_sendAuthReqServ(TmpUin, szUid, Translate("Automated authorization request\nYou automatically added by CheckContact(CHECKCONTACT chk) function\n////utiliyies.c:2350"));

                if (bTmpSendAdded)
                    m_pProto->icq_sendYouWereAddedServ(chk.dwUin, m_pProto->getSettingDword(NULL, "UIN", 0));

                //m_pProto->ShowPopUpMsg(chk.hContact, chk.dwUin, NickFromHandleUtf(chk.hContact), Translate("Added to temporary group"), LOG_NOTE); //temporary solution
            }
        }

        if (InDb)
        {
            if(chk.PSD != -1 && !Cancel)
            {
                if (!chk.PSD)
                    chk.PSD = 21;

                if (chk.PSD < 10)
                    SetUserStatus(chk.dwUin, 0, chk.PSD, 0);
                else if(m_pProto->getSettingWord(chk.hContact, "Status", ID_STATUS_OFFLINE) == ID_STATUS_OFFLINE && (/*bPSD &&*/ chk.PSD > 20)) //check for wrong PSD call
                    SetUserStatus(chk.dwUin, 0, chk.PSD, 0);
            }
        }
    }
}

DWORD CUserStatusSpy::icq_sendGetLocationInfo(HANDLE hContact, DWORD dwUIN, char *szUID)
{
    icq_packet packet;
    DWORD dwCookie;
    BYTE bUIDlen = getUIDLen(dwUIN, szUID);

    dwCookie = m_pProto->AllocateCookie(CKT_CHECKSPAMBOT, ICQ_LOCATION_QRY_USER_INFO, hContact, NULL);

    serverPacketInit(&packet, (WORD)(15 + bUIDlen));
    packFNACHeader(&packet, ICQ_LOCATION_FAMILY, ICQ_LOCATION_QRY_USER_INFO, 0, dwCookie);
    packDWord(&packet, 0x04);
    packUID(&packet, dwUIN, szUID);

    m_pProto->sendServPacket(&packet);

    return dwCookie;
}

void CUserStatusSpy::CheckSelfRemove()
{
    if(!DBGetContactSettingByte(NULL,ICQ_PROTOCOL_NAME,"PopSelfRem", 0)&&!DBGetContactSettingByte(NULL,ICQ_PROTOCOL_NAME,"LogSelfRem", 0))
        return;

    HANDLE hContact;
    hContact = m_pProto->FindFirstContact();

    while(hContact)
    {
        if (m_pProto->m_bSsiEnabled &&
                !m_pProto->getSettingWord(hContact, "ServerId", 0) && !m_pProto->getSettingWord(hContact, "SrvIgnoreId", 0) && !m_pProto->getSettingByte(hContact, "CheckSelfRemove", 0))
        {
            CHECKCONTACT chk = {0};
            chk.hContact=hContact;
            chk.dbeventflag = DBEF_READ;
            chk.icqeventtype=ICQEVENTTYPE_SELF_REMOVE;
            chk.logtofile=chk.historyevent=TRUE;
            //chk.msg="has removed himself from your Serverlist!";
            chk.PSD=-1;
            CheckContact(chk);
            DBWriteContactSettingByte(hContact, ICQ_PROTOCOL_NAME, "CheckSelfRemove", 1);
        }

        hContact = m_pProto->FindNextContact(hContact);
    }
    //NetLog_Server("Finished CheckSelfRemove thread");
    // MessageBox(0,"Finished CheckSelfRemove thread!","Warning",MB_OK);
}

DWORD CUserStatusSpy::SendDetectionPacket2(HANDLE hContact, char* szQuery, char* szNotify)
{
	char *szQueryBody;
	char *szNotifyBody;
	DWORD dwUin;
	int nBodyLen;
	char *szBody;
	DWORD dwCookie;
	cookie_message_data* pCookieData;

	m_pProto->getContactUid(hContact, &dwUin, NULL);

	//if (!CheckContactCapabilities(hContact, CAPF_XTRAZ) && !bForced)
	//   return 0; // Contact does not support xtraz, do not send anything

	szQueryBody = MangleXml(szQuery, strlennull(szQuery));
	szNotifyBody = MangleXml(szNotify, strlennull(szNotify));
	nBodyLen = strlennull(szQueryBody) + strlennull(szNotifyBody) + 41;
	szBody = (char*)SAFE_MALLOC(nBodyLen);
	nBodyLen = null_snprintf(szBody, nBodyLen, "<N><QUERY>%s</QUERY><NOTIFY>%s</NOTIFY></N>", szQueryBody, szNotifyBody);
	mir_free(szQueryBody);
	mir_free(szNotifyBody);

	// Set up the ack type
	pCookieData = m_pProto->CreateMessageCookie(MTYPE_SCRIPT_NOTIFY, ACKTYPE_CLIENT);
	dwCookie = m_pProto->AllocateCookie(CKT_MESSAGE, 0, hContact, (void*)pCookieData);

	// have we a open DC, send through that
	// if (gbDCMsgEnabled && IsDirectConnectionOpen(hContact, DIRECTCONN_STANDARD, 0))
	//   icq_sendXtrazRequestDirect(hContact, dwCookie, szBody, nBodyLen, MTYPE_SCRIPT_NOTIFY);
	// else
	m_pProto->icq_sendXtrazRequestServ(dwUin, dwCookie, szBody, nBodyLen, pCookieData);

	return dwCookie;
}

DWORD CUserStatusSpy::sendDetectionPacket(HANDLE hContact)
{
	char *szNotify;
	int nNotifyLen;
	DWORD dwCookie;
	nNotifyLen = 94 + UINMAXLEN;
	szNotify = (char*)SAFE_MALLOC(nNotifyLen);
	nNotifyLen = null_snprintf(szNotify, nNotifyLen, "<srv><id>cAwaySrv</id><req><id>AwayStat</id><trans>1</trans><senderId>%d</senderId></req></srv>", m_pProto->m_dwLocalUIN);
	dwCookie = SendDetectionPacket2(hContact, "<Q><PluginID>srvMng</PluginID></Q>", szNotify);
	return dwCookie;
}

extern void packEmptyMsg(icq_packet *packet);
extern void packServMsgSendHeader(icq_packet *p, DWORD dwSequence, DWORD dwID1, DWORD dwID2, DWORD dwUin, const char *szUID, WORD wFmt, WORD wLen);

void CUserStatusSpy::detectViaAuth(HANDLE hContact, DWORD dwCookie)
{
	DWORD dwUin, dwMyUin;
	uid_str szUid, szMyUid;
	icq_packet packet;
	DWORD dwID1;
	DWORD dwID2;

	// FIXME: Create second method that accepts UIN
	if (m_pProto->getContactUid(hContact, &dwUin, &szUid))
		return; // Invalid contact

	m_pProto->getContactUid(NULL, &dwMyUin, &szMyUid);

	dwID1 = time(NULL);
	dwID2 = RandRange(0, 0x00FF);

	packServMsgSendHeader(&packet, dwCookie, dwID1, dwID2, dwUin, NULL, 0x0004, 17);
	packWord(&packet, 0x0005);      // TLV(5)
	packWord(&packet, 0x0009);
	packLEDWord(&packet, dwMyUin);
	packByte(&packet, MTYPE_ADDED);
	packByte(&packet, 0);           // msg-flags
	packEmptyMsg(&packet);          // NTS
	packDWord(&packet, 0x00060000); // TLV(6)

	m_pProto->sendServPacket(&packet);
}
